﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

namespace ExcelDna.Integration.Rtd
{
    internal static class AsyncObservableImpl
    {
        static readonly Dictionary<AsyncCallInfo, Guid> _asyncCallIds = new Dictionary<AsyncCallInfo, Guid>();
        static readonly Dictionary<Guid, AsyncObservableState> _observableStates = new Dictionary<Guid, AsyncObservableState>();

        // This is the most general RTD registration
        // TODO: This should not be called from a ThreadSafe function. Check...?
        public static object ProcessObservable(string functionName, object parameters, ExcelObservableSource getObservable)
        {
            // TODO: Check here that registration has happened.
            // CONSIDER: Why not same problems with all RTD servers?

            AsyncCallInfo callInfo = new AsyncCallInfo(functionName, parameters);

            // Shortcut if already registered
            object value;
            if (GetValueIfRegistered(callInfo, out value))
            {
                return value;
            }

            // Actually register as a new Observable
            IExcelObservable observable = getObservable();
            return RegisterObservable(callInfo, observable);
        }

        // Make a one-shot 'Observable' from the func
        public static object ProcessFunc(string functionName, object parameters, ExcelFunc func)
        {
            return ProcessObservable(functionName, parameters,
                delegate { return new ThreadPoolDelegateObservable(func); });
        }

        static bool GetValueIfRegistered(AsyncCallInfo callInfo, out object value)
        {
            Guid id;
            if (_asyncCallIds.TryGetValue(callInfo, out id))
            {
                // Already registered.
                AsyncObservableState state = _observableStates[id];
                value = state.GetValue();
                return true;
            }
            value = null;
            return false;
        }

        // Register a new observable
        static object RegisterObservable(AsyncCallInfo callInfo, IExcelObservable observable)
        {
            // Check it's not registered already
            Debug.Assert(!_asyncCallIds.ContainsKey(callInfo));

            // Set up a new Id and ObservableState and keep track of things
            Guid id = Guid.NewGuid();
            _asyncCallIds[callInfo] = id;
            AsyncObservableState state = new AsyncObservableState(id, callInfo, observable);
            _observableStates[id] = state;

            // Will spin up RTD server and topic if required, causing us to be called again...
            return state.GetValue();
        }

        internal static void ConnectObserver(Guid id, ExcelRtdObserver rtdObserver)
        {
            // TODO: Checking...(huh?)
            AsyncObservableState state = _observableStates[id];
            // Start the work for this AsyncCallInfo, and subscribe the topic to the result
            state.Subscribe(rtdObserver);
        }

        internal static void DisconnectObserver(Guid id)
        {
            AsyncObservableState state = _observableStates[id];
            state.Unsubscribe();
            _observableStates.Remove(id);
            _asyncCallIds.Remove(state.GetCallInfo());
        }
    }
}
